module Anise

  module Annotative

    # The {Annotative::Attributes} mixin modifies the attr_* methods to allow easy
    # addition of annotations for attributes. It modifies the built in
    # attribute methods (attr, attr_reader, attr_writer and attr_accessor),
    # and any other custom `attr_*` methods, to allow annotations to be
    # added to them directly rather than requiring a separate annotating
    # statement.
    #
    #   class X
    #     extend Anise::Annotative::Attributes
    #
    #     attr :a, :valid => lambda{ |x| x.is_a?(Integer) }
    #   end
    #
    # See {Annotation} module for more information.
    #
    # @todo Currently annotated attributes alwasy use the standard
    #       annotator (:ann). In the future we might make this customizable.
    #
    module Attributes

      include Annotations

      #
      # When included into a class or module, {Annotation} is also
      # included and {Attribute::Aid} extends the class/module.
      #
      # @param base [Class, Module]
      #   The class or module to get features.
      #
      def self.extended(base)
        #inheritor :instance_attributes, [], :|
        base_class = (class << base; self; end)
        #base_class.attribute_methods.each do |attr_method|
        base.attribute_methods.each do |attr_method|
          define_annotated_attribute(base_class, attr_method)
        end
      end

      # TODO: Might #define_annotated_attribute make an acceptable class extension?

      #
      # Define an annotated attribute method, given an existing
      # non-annotated attribute method.
      #
      def self.define_annotated_attribute(base, attr_method_name)
        base.module_eval do
          define_method(attr_method_name) do |*args|
            args.flatten!

            harg={}; while args.last.is_a?(Hash)
              harg.update(args.pop)
            end

            raise ArgumentError if args.empty? and harg.empty?

            if args.empty?  # hash mode
              harg.each { |a,h| __send__(attr_method_name,a,h) }
            else
              klass = harg[:class] = args.pop if args.last.is_a?(Class)

              super(*args) #attr_method.call(*args)

              args.each{|a| ann(a.to_sym,harg)}

              instance_attributes!.concat(args)  #merge!

              # Use this callback to customize for your needs.
              if respond_to?(:attr_callback)
                attr_callback(self, args, harg)
              end

              # return the names of the attributes created
              return args
            end
          end
        end
      end

      #
      # Instance attributes, including inherited attributes.
      #
      def instance_attributes
        a = []
        ancestors.each do |anc|
          next unless anc < Attributes
          if x = anc.instance_attributes!
            a |= x
          end
        end
        return a
      end

      #
      # Local instance attributes.
      #
      def instance_attributes!
        @instance_attributes ||= []
      end

      #
      # Return list of attributes that have a :class annotation.
      #
      #   class MyClass
      #     attr_accessor :test
      #     attr_accessor :name, String, :doc => 'Hello'
      #     attr_accessor :age, Fixnum
      #   end
      #
      #   MyClass.instance_attributes # => [:test, :name, :age, :body]
      #   MyClass.classified_attributes # => [:name, :age]
      #
      def classified_attributes
        instance_attributes.find_all do |a|
          self.ann(a, :class)
        end
      end

      #
      # This defines a simple adjustment to #attr to allow it to handle the boolean argument and
      # to be able to accept attributes. It's backward compatible and is not needed for Ruby 1.9
      # which gets rid of the secondary argument (or was suppose to!).
      #
      def attr(*args)
        args.flatten!
        case args.last
        when TrueClass
          args.pop
          attr_accessor(*args)
        when FalseClass, NilClass
          args.pop
          attr_reader(*args)
        else
          attr_reader(*args)
        end
      end

    end

  end

end

# Copyright (c) 2006,2011 Thomas Sawyer. All rights reserved. (BSD-2-Clause License)
